using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Threading;
using System.IO;
using System.Diagnostics;
using BrainTechLLC.Communications;
using AcedUtils;

namespace BrainTechLLC.Communications
{
  /// <summary>
  /// Peer-to-peer (socket) communications
  /// </summary>
  public class P2PCommunicationsLibrary : CommunicationsLibrary, ICommunications
  {
    public override CommunicationsResponse BeginWorkerThreads()
    {
      if (InitializedAndActive) return CommunicationsResponse.Success;
      if (ConnectionHub == null) { return AcceptIncomingConnections(IPAddress.None, 4009); }
      try
      {
        ConnectionHub.BeginListening();
        ConnectionHub.BeginSendReceiveThreads();
        ConnectionHub.BeginProcessingThread();
      }
      catch (Exception ex) { throw new Exception("Error Starting Worker Threads", ex); }
      fInitialized = true;
      return CommunicationsResponse.Success;
    }

    /// <summary>
    /// Serialize and broadcast notification of a screen resolution change or zoom-in
    /// </summary>
    /// <param name="nWidth"></param>
    /// <param name="nHeight"></param>
    /// <returns></returns>
    public override CommunicationsResponse BroadcastScreenResolutionChangeOrZoom(int nWidth, int nHeight)
    {
      if (ConnectionHub != null)
      {
        using (MemoryStream msOutgoingPacket = new MemoryStream(256))
        {
          using (BinaryWriter bw = new BinaryWriter(msOutgoingPacket))
          {
            // 2 represents a screen size change
            bw.Write((short)MessageTypes.HostResolutionChange);
            bw.Write((short)Convert.ToInt16(nWidth));
            bw.Write((short)Convert.ToInt16(nHeight));
            bw.BaseStream.Seek(0, SeekOrigin.Begin);
            int srcOrigLen = (int)bw.BaseStream.Length;

            byte[] rgMessageToBroadcast =
              CommConnection.CreateCommandPacket(
                msOutgoingPacket.GetBuffer(),
                srcOrigLen);
            ConnectionHub.BroadcastOut(
              rgMessageToBroadcast,
              srcOrigLen + CommConnection.nPacketHeaderSize + CommConnection.nPacketLengthPrefixSize,
              CommConnection.eMessagePriority.High
            );
            bw.Close();
          }
        }
      }
      return CommunicationsResponse.Success;
    }

    /// <summary>
    /// Serialize and Broadcast screen update notification packets
    /// </summary>
    /// <param name="screenUpdateDataPacket"></param>
    /// <param name="nMaxLength"></param>
    /// <returns></returns>
    public override CommunicationsResponse BroadcastScreenUpdateNotification(
      ScreenUpdateDataPacket screenUpdateDataPacket, int nMaxLength
      )
    {
      if (screenUpdateDataPacket == null ||
          screenUpdateDataPacket.rgScreenUpdateData == null ||
          screenUpdateDataPacket.rgScreenUpdateData.Count == 0)
        return CommunicationsResponse.Success;

      if (ConnectionHub != null)
      {
        MemoryStream msOutgoingPacket = new MemoryStream(64000);
        BinaryWriter bw = new BinaryWriter(msOutgoingPacket);
        // 1 represents a screen update packet
        bw.Write((short)MessageTypes.ScreenUpdatePacket);
        bw.Write((short)Convert.ToInt16(screenUpdateDataPacket.rgScreenUpdateData.Count));
        int nCompLength = 0;
        for (int i = 0; i < screenUpdateDataPacket.rgScreenUpdateData.Count; i++)
        {
          ScreenUpdateData screenUpdateData =
            screenUpdateDataPacket.rgScreenUpdateData[i];

          // Compress the screen difference data
          // compress each packet individually
          screenUpdateData.SetByteContentType(ByteBufferContentType.CompressedDifferenceMap);

          bw.Write((short)Convert.ToInt16(screenUpdateData.Left));
          bw.Write((short)Convert.ToInt16(screenUpdateData.Top));
          bw.Write((short)Convert.ToInt16(screenUpdateData.Width));
          bw.Write((short)Convert.ToInt16(screenUpdateData.Height));
          // Note this is the compressed data length
          bw.Write((int)screenUpdateData.nDataLength);
          nCompLength += screenUpdateData.nDataLength;
          // If this is a very large update packet, it's worth it just to resend the whole screen image (JPG)
          if (nCompLength > nMaxLength) { return CommunicationsResponse.BroadcastFullImageInstead; }
        }

        for (int i = 0; i < screenUpdateDataPacket.rgScreenUpdateData.Count; i++)
        {
          ScreenUpdateData screenUpdateData = screenUpdateDataPacket.rgScreenUpdateData[i];
          bw.Write(screenUpdateData.byteData, 0, screenUpdateData.nDataLength);
        }
        bw.BaseStream.Seek(0, SeekOrigin.Begin);
        int srcOrigLen = (int)bw.BaseStream.Length;
        byte[] rgMessageToBroadcast = CommConnection.CreateCommandPacket(msOutgoingPacket.GetBuffer(), srcOrigLen);
        ConnectionHub.BroadcastOut(
          rgMessageToBroadcast,
          srcOrigLen + CommConnection.nPacketHeaderSize + CommConnection.nPacketLengthPrefixSize,
          CommConnection.eMessagePriority.Normal
        );
      }
      return CommunicationsResponse.Success;
    }

    public override CommunicationsResponse BroadcastDataOut( byte[] rgBytesToSend, int nLen )
    {
      if (ConnectionHub != null) { ConnectionHub.BroadcastOut(rgBytesToSend, nLen, CommConnection.eMessagePriority.Normal); }
      return CommunicationsResponse.Success;
    }

    public override CommunicationsResponse EstablishOutgoingConnection( string sRemoteHost, int nPort, SupportedCommunicationsProtocol Protocol
      )
    {
      if (Protocol != SupportedCommunicationsProtocol.TCPIP) return CommunicationsResponse.Error;
      if (ConnectionHub == null) { AcceptIncomingConnections(IPAddress.Any, 9996); }

      if (outgoingConnection != null)
      {
        outgoingConnection.NeedsClosing = true;
        outgoingConnection = null;
      }
      try
      {
        outgoingConnection = new CommConnection(ConnectionHub, null);
        if (outgoingConnection.Connect(sRemoteHost, nPort) == false) { return CommunicationsResponse.Error; }
        // Socket options.  Can be tuned as desired
        outgoingConnection.Descriptor.NoDelay = false;
        outgoingConnection.Descriptor.ReceiveBufferSize = 16384;
        outgoingConnection.Descriptor.SendBufferSize = 16384;
        outgoingConnection.Descriptor.Client.Blocking = true;
        outgoingConnection.fNewConnection = true;
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.ToString());
        throw new Exception("Error connecting to remote host", ex);
      }
      fNewConnection = true;
      return CommunicationsResponse.Success;
    }

    /// <summary>
    /// Creates a new connection "hub" and uses this "hub" to begin accepting incoming connections
    /// </summary>
    /// <param name="localIP"></param>
    /// <param name="nPort"></param>
    /// <returns></returns>
    public override CommunicationsResponse AcceptIncomingConnections( IPAddress localIP, int nPort )
    {
      if (ConnectionHub != null)
      {
        ConnectionHub.Stop = true;
        // give the thread some time to shut down all workers/etc
        Thread.Sleep(2000);
        ConnectionHub = null;
      }
      ConnectionHub = new CommConnection(localIP, nPort);
      ConnectionHub.commInterfaceToUse = this;
      fInitialized = false;    
      return CommunicationsResponse.Success;
    }
  }
}
